/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, AstTreeUtils, ts } from "arkanalyzer/lib";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { MatcherCallback, MatcherTypes, FileMatcher } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from "../../model/Fix";

interface Issue {
    line: number;
    column: number;
    columnEnd: number;
    message: string;
    filePath: string;
    fixCode: string; // 修复后的代码片段
}

type stringOpt = 'always' | 'smart' | 'allow-null';
type stringOpt1 = 'always' | 'never' | 'ignore';
type Options = [
    stringOpt,
    {
        null: stringOpt1
    }
]

interface EqeqeqOptions {
    mode?: 'always' | 'smart' | 'allow-null';
    null?: 'always' | 'never' | 'ignore';
}

type InferredType =
    'string' | 'number' | 'boolean' | 'object' |
    'null' | 'undefined' | 'regexp' | 'typeof' | 'unknown';

// 根据 ESLint 的 JavaScript 实现：
// mode: 默认值为 "always"。
// null: 当 mode 是 "always" 时，默认值为 "always"。
// 当 mode 是 "smart" 时，null 的值被忽略（因为 "smart" 模式下会自动处理 null）。
export class EqeqeqCheck implements BaseChecker {
    metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: "docs/eqeqeq-check.md",
        description: "Require the use of === and !==."
    };
    //默认
    private defaultOptions: Options = [
        'always', // 默认值
        {
            null: 'always', // 默认值
        }
    ];
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };
    private config: EqeqeqOptions = {
        mode: 'always', // 默认值
        null: 'always' // 默认值
    };
    public registerMatchers(): MatcherCallback[] {
        const fileMatcher: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check,
        };
        return [fileMatcher];
    }

    public check = (target: ArkFile) => {
        this.parseConfig();
        if (target instanceof ArkFile) {
            let code = target.getCode();
            this.checkEqeqeq(code, target);
        }
    }

    private parseConfig(): void {
        this.defaultOptions = this.rule && this.rule.option[0] ? this.rule.option as Options : this.defaultOptions;
        this.config.mode = this.defaultOptions[0];
        this.config.null = this.defaultOptions[1]?.null;
        if (this.config.mode === 'allow-null') {
            this.config.mode = 'always';
            this.config.null = 'ignore';
            return;
        }
        // 确保 null 的默认值与 ESLint 一致
        if (this.config.mode === "always" &&
            this.config.null === undefined) {
            this.config.null = "always";
        }
    }

    private checkEqeqeq(code: string, arkFile: ArkFile): void {
        const methodAst = AstTreeUtils.getASTNode(arkFile.getName(), code);
        const isTypeOf = (node: ts.Node): boolean => {
            return ts.isTypeOfExpression(node);
        }

        const isTypeOfBinary = (node: ts.BinaryExpression): boolean => {
            return isTypeOf(node.left) || isTypeOf(node.right);
        }

        const areLiteralsAndSameType = (node: ts.BinaryExpression): boolean => {
            const isLeftLiteral = ts.isLiteralExpression(node.left) || isBooleanLiteral(node.left);
            const isRightLiteral = ts.isLiteralExpression(node.right) || isBooleanLiteral(node.right);

            if (!isLeftLiteral || !isRightLiteral) {
                return false; // 不是字面量，直接返回 false
            }

            // 获取字面量的值
            const leftValue = getLiteralValue(node.left);
            const rightValue = getLiteralValue(node.right);

            // 检查是否为相同类型的字面量
            return typeof leftValue === typeof rightValue;
        }

        const isBooleanLiteral = (node: ts.Node): boolean => {
            return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword;
        }

        const getLiteralValue = (node: ts.Node): string | number | boolean | null => {
            switch (node.kind) {
                case ts.SyntaxKind.StringLiteral:
                    return (node as ts.StringLiteral).text; // 确保访问的是 StringLiteral 类型
                case ts.SyntaxKind.NumericLiteral:
                    return parseFloat((node as ts.NumericLiteral).text); // 确保访问的是 NumericLiteral 类型
                case ts.SyntaxKind.TrueKeyword:
                    return true; // 布尔字面量 true
                case ts.SyntaxKind.FalseKeyword:
                    return false; // 布尔字面量 false
                case ts.SyntaxKind.NullKeyword:
                    return null; // null 字面量
                default:
                    return null; // null 字面量;
            }
        }

        const isNullCheck = (node: ts.BinaryExpression): boolean => {
            return (
                (node.left.kind === ts.SyntaxKind.NullKeyword) || // 检查左侧是否为 null
                (node.right.kind === ts.SyntaxKind.NullKeyword)   // 检查右侧是否为 null
            );
        }

        const isLeftNullCheck = (node: ts.BinaryExpression): boolean => {
            return node.left.kind === ts.SyntaxKind.NullKeyword;
        }

        const isRightNullCheck = (node: ts.BinaryExpression): boolean => {
            return node.right.kind === ts.SyntaxKind.NullKeyword;
        }

        const inferType = (node: ts.Node): InferredType => {
            if (ts.isStringLiteral(node)) return 'string';
            if (ts.isNumericLiteral(node)) return 'number';
            if (node.kind === ts.SyntaxKind.TrueKeyword ||
                node.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
            if (node.kind === ts.SyntaxKind.NullKeyword) return 'null';
            if (node.getText().includes('typeof ')) return 'typeof';
            return 'unknown';
        };

        const isSameType = (left: ts.Node, right: ts.Node): boolean => {
            const leftType = inferType(left);
            const rightType = inferType(right);
            // 处理 null 和 undefined 的特殊情况
            if (leftType === 'null' && rightType === 'undefined') return false; // null == undefined 是允许的，但类型不同
            if (leftType === 'undefined' && rightType === 'null') return false;
            if (leftType === 'typeof' && rightType === 'string') return true;
            if (rightType === 'typeof' && leftType === 'string') return true;
            return leftType === rightType && leftType !== 'unknown';
        };

        const getOperatorRange = (node: ts.BinaryExpression, sourceFile: ts.SourceFile): [number, number] => {
            const operatorPos = node.operatorToken.getStart(sourceFile);
            return [
                operatorPos,
                operatorPos + (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken ? 2 : 2),
            ];
        }

        const checkNode = (node: ts.Node) => {
            if (ts.isBinaryExpression(node)) {
                const operator = node.operatorToken.kind;
                const isEqualityCheck = operator === ts.SyntaxKind.EqualsEqualsToken ||
                    operator === ts.SyntaxKind.ExclamationEqualsToken;
                // 处理 null 特殊配置
                const enforceRuleForNull = this.config.null === 'always';
                const enforceInverseRuleForNull = this.config.null === 'never';
                if (!isEqualityCheck && !enforceInverseRuleForNull) return;
                const isNull = isNullCheck(node);

                //["always", { null: "never" }]
                if ((isNull && enforceInverseRuleForNull && isEqualityCheck) ||
                    (!isNull && enforceInverseRuleForNull && operator === ts.SyntaxKind.EqualsEqualsEqualsToken)) {
                    return;
                }
                const expectedOperator = operator === ts.SyntaxKind.EqualsEqualsToken ? '===' : '!==';
                // 处理不同配置模式
                switch (this.config.mode) {
                    case 'smart':
                        if (isTypeOfBinary(node) ||
                            areLiteralsAndSameType(node) || isNull) {
                            return
                        };
                        break;
                }
                const operatorStart = node.operatorToken.getStart(methodAst);
                const operatorLine = methodAst.getLineAndCharacterOfPosition(operatorStart).line;
                const operatorColumn = methodAst.getLineAndCharacterOfPosition(operatorStart).character;
                if (enforceInverseRuleForNull && isNull) {
                    const isTripleEqual = operator === ts.SyntaxKind.EqualsEqualsEqualsToken;
                    const expectedOperator1 = isTripleEqual ? '==' : '!=';
                    const [startRange, endRange] = getOperatorRange(node, methodAst);
                    const resultIssue: Issue = {
                        line: operatorLine + 1,
                        column: operatorColumn + 1,
                        columnEnd: operatorColumn + 1 + node.operatorToken.getText().length,
                        message: `Expected '${expectedOperator1}' and instead saw '${node.operatorToken.getText()}'`,
                        filePath: arkFile.getFilePath() ?? '',
                        fixCode: expectedOperator1
                    }
                    let ruleFix;
                    if (isSameType(node.left, node.right)) {
                        ruleFix = this.createFix(startRange, endRange, expectedOperator1);
                    }
                    this.addIssueReport(resultIssue, ruleFix);
                    return;
                }
                if (!enforceRuleForNull && isNull) return;
                const [startRange, endRange] = getOperatorRange(node, methodAst);
                const fix = code.substring(0, startRange) + expectedOperator + code.substring(endRange);

                const resultIssue: Issue = {
                    line: operatorLine + 1,
                    column: operatorColumn + 1,
                    columnEnd: operatorColumn + 1 + node.operatorToken.getText().length,
                    message: `Expected '${expectedOperator}' and instead saw '${node.operatorToken.getText()}'`,
                    filePath: arkFile.getFilePath() ?? '',
                    fixCode: expectedOperator
                }
                let ruleFix;
                if (isSameType(node.left, node.right)) {
                    ruleFix = this.createFix(startRange, endRange, expectedOperator);
                }
                this.addIssueReport(resultIssue, ruleFix);
            }
            ts.forEachChild(node, checkNode);
        };
        // 从根节点开始遍历
        checkNode(methodAst);
    }

    private createFix(start: number, end: number, code: string): RuleFix {
        return { range: [start, end], text: code };
    }

    private addIssueReport(issue: Issue, ruleFix?: RuleFix) {
        this.metaData.description = issue.message;
        const severity = this.rule.alert ?? this.metaData.severity;
        const defects = new Defects(issue.line, issue.column, issue.columnEnd, this.metaData.description, severity, this.rule.ruleId, issue.filePath,
            this.metaData.ruleDocPath, true, false, (ruleFix != undefined ? true : false));
        this.issues.push(new IssueReport(defects, ruleFix));
        RuleListUtil.push(defects);
    }
}